package cz.cvut.kadledav.paa.weight3sat;

import cz.cvut.kadledav.paa.weight3sat.bo.Instance;
import cz.cvut.kadledav.paa.weight3sat.bo.SolutionState;

import java.util.*;

/**
 * User: David Kadlecek
 * Date: 2.1.14
 */
public class SimulatedAnnealing {

    public static final String START_TEMPERATURE = "startTemperature";
    public static final String FINAL_TEMPERATURE = "finalTemperature";
    public static final String COOLING_COEFFICIENT = "coolingCoefficient";
    public static final String INNER_LOOP = "innerLoop";
    public static final String MAKE_CHART = "makeChart";

    private double startTemperature;
    private double finalTemperature;
    private double coolingCoefficient;
    private double innerLoop;
    private boolean makeChart;

    private SolutionState returnState;
    private boolean[] evaluation;
    private final Instance instance;

    final private Random random = new Random();

    private int CHART_STEPS = 5000;
    private List<Double> tempHistory = new LinkedList<>();
    private List<Double> costHistory = new LinkedList<>();

    public SimulatedAnnealing(Instance instance,Map<String, Object> params) {
        startTemperature = Double.parseDouble(params.get(START_TEMPERATURE).toString());
        finalTemperature = Double.parseDouble(params.get(FINAL_TEMPERATURE).toString());
        coolingCoefficient = Double.parseDouble(params.get(COOLING_COEFFICIENT).toString());
        innerLoop = Double.parseDouble(params.get(INNER_LOOP).toString());
        makeChart = params.get(MAKE_CHART) != null;
        evaluation = new boolean[instance.getNumberOfVariables()];
        returnState = SolutionState.createFromInstanceAndEvaluation(instance, evaluation);
        this.instance = instance;
    }

    public void compute() {
        double actualTemp = startTemperature;
        double stateCost = 0;
        int stateWeight = 0;
        int iteration = 0;

        while(true){
            if(actualTemp <= finalTemperature ){
                break;
            }
            for (int i = 0; i < innerLoop; i++) {

                final SolutionState newSolutionState = computeNewState();
                if(acceptNew(actualTemp,newSolutionState)){
                    returnState = newSolutionState;
                }
                if(makeChart){
                    if(iteration%CHART_STEPS == 0){
                        costHistory.add((double)returnState.getOptCriteria());
                        tempHistory.add(actualTemp);
                    }
                    iteration++;
                }

            }
            actualTemp = coolingCoefficient * actualTemp;
        }
        if(makeChart){
            ScatterGraph.showScatterGraph(costHistory.iterator(),String.format("Tstart:%s , cooling:%s, innerLoop:%s",String.valueOf(startTemperature),String.valueOf(coolingCoefficient),String.valueOf(innerLoop)),CHART_STEPS);
            System.out.println("max:" +returnState.getOptCriteria()+", nespnenych:"+returnState.getNumberOfUnsatisfiableClauses()+", váha"+returnState.getWeight());
            throw new RuntimeException("THE END");
        }
    }

    private boolean acceptNew(double actualTemp, SolutionState newSolutionState) {
        final int newValue = newSolutionState.getOptCriteria();
        final int currentValue = returnState.getOptCriteria();
        if(newValue >= currentValue){

//            if(newValue>=0){
//                System.out.println("aa");
//            }
            return true;
        }
        return Math.random() < Math.exp(-(currentValue - newValue) / actualTemp);
    }

    private SolutionState computeNewState() {
//        final int changes = Math.max(random.nextInt(evaluation.length/10),1);
//        for (int i = 0; i < changes; i++) {

            int change = random.nextInt(evaluation.length);
            evaluation[change] = !evaluation[change];
//        }
        return SolutionState.createFromInstanceAndEvaluation(instance,evaluation);
    }

    public SolutionState getResult() {
        return returnState;
    }
}
